3

声明:本文并非博主原创,而是来自对《Laravel 4 From Apprentice to Artisan》阅读的翻译和理解,当然也不是原汁原味的翻译,能保证90%的原汁性,另外因为是理解翻译,肯定会有错误的地方,欢迎指正。

欢迎转载,转载请注明出处,谢谢!

控制反转容器

基础绑定

上一张,我们学习了依赖注入,接下来,我们继续探索的是“控制反转”或者叫“依赖倒置”。后面我们使用IoC容器来代指如上的定义。IoC容器使类的依赖管理变的非常方便,Laravel的核心就是由这种强大的容器思想来驱动的。IoC容器是Laravel框架重要的组成部分,他将框架中所有组件组织在一起工作。事实上Laravel的Application类就是集成自Container容器类。

控制反转容器(IoC Container)

控制反转使依赖注入变得更加便捷。如果在容器中定义好了相关的类或者接口(约定),我们如何在程序中解析、并注入这些对象呢?

Laravel应用中,IoC容器可以使用依照门面设计模式实现的App类来访问容器。容器中包含了各式各样的方法,这里我们只介绍一些比较基础的方法。让我们以上一章中的BillerInterfaceBillingNotifierInterface为基础,来继续探讨使用Stripe1实现的支付功能。我们可以将Stripe的接口实现按照如下代码绑定到容器中:

App::bind('BillerInterface', function()
{
    return new StripeBiller(App::make('BillingNotifierInterface'));
})

注意这里,我们在绑定了BillerInterface的同时,也注入了BillingNotifierInterface接口实现的具体类,所以要将接口绑定到容器中:

App::bind('BillingNotifierInterface', function()
{
    return new EmailBillingNotifier;
});

如上,我们可以理解,容器就是各种接口对应实现类的绑定的地方。一旦他们绑定到容器中,我们就能在整个应用中的任意地方解析并使用他。我们甚至可以在解析器中继续将其他内容绑定到容器。

有瑕疵?

Laravel控制反转容器是Fabien Potencier实现的Pimple2控制翻转容器的一种替代方案。如果你已经在项目中使用到Pimple,尽可安心的升级为Illuminate Container3组件,他为您提供了更多好用的特性!

一旦使用了容器,切换接口实现就是一件非常简单的事情,简单到一行代码就能搞定:

class UserController extends BaseController{

    public function __construct(BillerInterface $biller)
    {
        $this->biller = $biller;
    }
}

控制器通过容器实例化后,包含EmailBillingNotifierStripeBiller类就会随着容器注入到控制器中。现在,若想更换通知器的实现方式,只需要接口绑定的实现即可:

App::bind('BillingNotifierInterface', function()
{
    return new SmsBillingNotifier;
});

现在,无需担心项目中到底哪里用到了这个通知器,只需实现新的SmsBillingNotifier类即可。利用这种方式,我们的应用可以在不同的场景下实现快速切换。

是不是感觉这种切换实现的方式很高大上。想象一下,如果想将短信通知器的服务提供商更换为Twilio。我们只须开发一个使用Twilio通知的实现类,并替换掉绑定到容器中的接口对应的实现类就好。如果在向Twilio的过度中出现了问题,我们还能快速的将容器中接口绑定的类替换回原来的服务,这里只需要那么一丁点改变就能快速实现需求变更。可以看到,依赖注入的优点是超乎想象的。再多几个例子?

好吧!接着往下看。 有时候,我们想在整个应用中对一个类只进行一次解析,一次实例化。使用容器中的singleton方法即可:

App::singleton('BillingNotifierInterface', function()
{
    return new SmsBillingNotifier;
});

现在,容器一蛋解析了订单通知类,在接下来的整个请求中都会使用同一个已实例化的实例。

容器中的intance方法和singleton有点类似;区别在于你可以传入一个已存在的对象来更新接口绑定的实例,在后续的使用中,容器都将使用到这个新的对象。

$notifier = new SmsBillingNotifier;
App::instance('BillingNotifierInterface', $notifier);

现在我们已经熟悉使用容器进行闭包回调的基础方法,接下来,让我们深入挖掘下他更强大的功能:反射。

容器的独立使用

即使没有使用Laravel框架,我们仍然可以在项目中使用Composer安装illuminate/container组件来使用Laravel的控制反转容器。

反射

Laravel容器的一个强大的特性就是能通过反射自动解析依赖。反射具有检测类及其方法的能力。比如,PHP中的ReflectionClass类允许你检测一些方法在给定的类中是否可用。PHP函数method_exists也是反射的一种形式。看看下面的代码,让我们来把玩一下:

$reflection = new ReflectionClass('StripeBiller');

var_dump($reflection->getMethods());

var_dump($reflection->getConstants());

通过使用PHP的这种特性,Laravel可以实现一些有趣的功能!例如,如下代码:

class UserController extends BaseController {

    public function __construct(StripeBiller $biller)
    {
        $this->biller = $biller;
    }

}

如上控制器初始化时需要传入StripeBiller类型的对象,我们可以通过反射进行类型检测。当Laravel容器没有绑定相应的解析器,它就会通过反射尝试解析该类。流程大致如下:

  1. 容器中有无StripeBiller解析器?

  2. 没有解析器?映射类StripeBiller判断其依赖。

  3. 递归的解析StripeBiller类的所有依赖。

  4. 通过ReflectionClass->newInstanceArgs()实例化一个新的StripeBiller

可以看到,容器为你做了很多繁重的工作,使你能释放更多的时间用于编码各种逻辑的代码类。这就是Laravel容器特有的强大特性,也是它能够胜任构建大型应用的必杀器。

现在,我们控制器中的代码修改成这样,这会怎样?

class UserController extends BaseController
{
    public function __construct(BillerInterface $biller)
    {
        $this->biller = $biller;
    }
}

假如我们没有对BillerInterface进行绑定,那么容器如何注入其依赖的类呢?注意,接口只是个约定,他是不能进行实例化的。在没有给定任何信息的情况下,容器是无法实例化相关依赖的。所以我们需要使用bind方法来为接口指定一个默认的类的实现:

App::bind('BillerInterface','StripBiller');

这里我们把字符串替换成一个闭包传入容器,他会告诉容器任何情况下总是使用StripeBiller这个实现自BillerInterface接口的类。这里,我们又可以只修改一行代码,就能进行替换掉容器中的绑定的逻辑了。假如我们想使用余额支付来代替现有的支付,我们只需要完成继承自BillerInterface接口的实现类BalanceInterfacez,同时修改下容器中的绑定:

App::bind('BillerInterface', 'BalancedBiller');

应用就会自动解析并使用这个新的支付方式。

同样我们也可以使用singleton方法绑定接口,这样在整个请求周期内容器只会进行一次实例化。

App::singleton('BillerInterface', 'StripeBiller');

掌握容器

想要深入理解Laravel容器?那就通读下代码吧!容器只有一个类文件IlluminateContainerContainer。当你看完这个文件代码后,肯定能对容器有更深、更全面的认识。

          • -

2015-04-02 第二次阅读修正。

2015-02-14 第一次翻译发布。


laravel
101 声望55 粉丝